Chapter 15 PACKAGES PACKAGES ARE WHY ADA EXISTS _________________________________________________________________ One of the biggest advantages of Ada, over most other programming languages, is its well defined system of modularization and separate compilation. Even though Ada allows separate compilation, it maintains the strong type checking among the various compilations by enforcing rules of compilation order and compatibility checking. Ada uses separate compilation, but FORTRAN, as a classic example, uses independent compilation, in which the various parts are compiled with no knowledge of the other compilation units with which they will be combined. As we progress through this material, and additional material to come later, do not be discouraged if you find many things to keep in mind when doing separate compilations. The rules are not meant to be roadblocks, but are actually benefits for you when you are working on a large complex system. LET'S LOOK AT A PACKAGE _________________________________________________________________ Examine the file named ADDERPKG.ADA for our ================ first example of a separately compiled Ada ADDERPKG.ADA package. A package, as the term is used in Ada, ================ refers to a collection of related entities, the collection being composed of procedures, functions, variables, constants, types, subtypes, and even other packages. In our present example, the package is composed of one type, and one procedure. THE SPECIFICATION OF THE PACKAGE _________________________________________________________________ Lines 4 through 8 define the specification of the package named AdderPkg, which is actually a very simple package, and purposely kept simple for illustrative purposes. The type MY_ARRAY is defined, as well as the procedure heading for Add_Em_Up in the specification part of the package. The only things a user needs to know about the package in order to use it are defined in the package specification, so it becomes the interface to the outside world. With a few defining statements, this is all the user would need to know about the package, and he could be kept away from the actual details of how the procedure does its job. We will see more about the topic of information hiding later in this chapter. Page 15-1 Chapter 15 - Packages Note that anything that is declared in the specification part of the package can be used in any other package that with's this package. We have an unconstrained array type declared in line 5 which we have not yet studied in this tutorial. The range for the subscript is defined by the "<>", which is a box that must be filled in later when we define the actual type. We will cover this in detail in the chapter on advanced array topics. THE BODY OF THE PACKAGE _________________________________________________________________ Lines 12 through 23 give the body of the package, and it is distinguished from the specification by the reserved word body in its header, and by the fact that the procedure is completely defined here. Anything defined or declared in the specification part of the package is available for use here, just as if it were defined at the beginning of this section. The procedure header is redefined here in full, and it must exactly match the definition in the specification or you will get a compile error and no compilation. There is nothing different about this procedure from any other procedure we have seen, it just happens to be in the package body. Note that any types, variables, constants, procedures, or functions, can be declared in the package body for use within the body, but none of them are available outside of the package because they are not defined in the specification part of the package. The variable declared in line 15 named Total is not available outside of this package and there is no way it can be referred to outside of the package without being declared in the package specification. In fact, since it is embedded within the procedure, it would be hidden anyway, but the fact remains that no entities are available outside of the package except those that are declared in the specification of the package. Note that since the array has no defined limits, we must use attributes to define the loop range. Even though you may find this a bit confusing, you will appreciate the flexibility found here after we study some of the more advanced topics. IT CAN BE COMPILED BUT NOT EXECUTED _________________________________________________________________ This file can be compiled, but it cannot be linked and executed because it is not a complete program, it is only a package containing a type and a procedure which can be called from another program just like we have been calling the procedure Put in the Text_IO package throughout this tutorial. Page 15-2 Chapter 15 - Packages One other point must be made before we look at a program to use this package, the specification and the body do not need to be in the same file, they can be contained in separate files and compiled separately. If this is the case, the specification must be compiled prior to compiling the body because the body uses information generated during compilation of the specification. Actually, even though they are in one file in this example, they are considered separately by the compiler, being compiled in serial fashion. This file is said to be composed of two compilation units. FILENAME VERSUS PACKAGE NAME _________________________________________________________________ In all of the example programs so far in this tutorial we have used the same name for the filename and the program name, or the procedure name. This is not really necessary, but it was felt that an additional level of complexity should be delayed until later. We have arrived at the time when an explanation is needed. When Ada compiles a program, it adds the result of the compilation into its library using the program name. This library entry includes all information needed to link the program later with the other needed library entries. Later when you use the linker supplied with your compiler, the linker will collect all of the necessary compiled packages and subprograms and combine them into an executable program. In order to compile a program, it is necessary to give the filename so the operating system can find the program for the Ada compiler, but in order to link a program, the filename is no longer of any use because the name that really matters is the program name, and the program name is what the linker uses. Ada allows identifiers to be of any arbitrary length, but your operating system has some length limit, eight if you are using MS_DOS, therefore the Ada compiler may need to somehow make up a new name if intermediate results are put into individual files. It will be up to you to determine how your compiler stores intermediate results and how it makes up filenames for these results. For simplicity, therefore, all program names have been limited to eight characters, and the same name is used for the filename throughout most of this tutorial. NOW TO USE THE NEW PACKAGE _________________________________________________________________ The example file named ADDER1.ADA, illustrates how to use the previously studied package. There is nothing different about this program from any other program we have used except that it uses the Page 15-3 Chapter 15 - Packages package we defined and named AdderPkg, which it ================ acknowledges in lines 5 and 6 where it tells the ADDER1.ADA system to with the package and to use it in the ================ program at hand. It also uses the renaming statement in line 19 which we will discuss later. The remainder of the program is simple, and you should have no trouble deciphering it. The primary purpose of these two examples is to illustrate how to write a library package. In line 17 we declare an array of type MY_ARRAY and at this time we supply the range limits for the subscript. You may begin to see the flexibility in this method of array declaration, but we will study it in detail later. THE with CLAUSE _________________________________________________________________ It is finally time for a complete definition of just what the with clause does for us. When we with a package into our program, we are telling the system that we wish to have everything that is declared in the specification of that package available for our use in this program. Accordingly, the system will look at the items declared in the specification for that package and every time we use one of those items, it will see that we have the correct number of parameters and that we have the types declared for each parameter correctly. Note that this happens during compilation and explains why Ada is said to be compiled separately, but not independently of other compilation units. In a sense, the resources available in the withed package act as though they are extensions to the Ada programming language. Because of this, the with clause is called a context clause. When you arrive at the linking operation, the withed packages are automatically added into the executable file, as are any other packages that are withed by the package you have explicitly told the system to with into your program. Note that the package named STANDARD is automatically withed into every Ada program, subprogram, or package. The package named STANDARD defines many of the predefined entities such as INTEGER, BOOLEAN, FLOAT, etc. In a large program, it is possible to with the same package several times since it is used in several packages. You can be assured that only one copy of the package will be included during the linking operation. Multiple copies will not be stored. One other point must be made before leaving this topic, and that is the fact that all with clauses must be at the beginning of the program or package. There is a good reason for this. The dependent packages, and hence the overall program structure must be given at the beginning of each package making it easy to ascertain the overall structure without being forced to search through the entire listing for each package. Page 15-4 Chapter 15 - Packages THE use CLAUSE _________________________________________________________________ The use clause in Ada allows you to use a shorthand when naming procedures, functions, variables, etc, from a package. Instead of using the extended naming convention, or the so called "dot" notation, and including the package name followed by the procedure name, dotted together, you can simply use the procedure name and let the system figure out what package it is coming from. In most of the programs we have studied so far, we have included the Text_IO package in a use clause. If the use clause were omitted we would have to identify the package in each use of the procedures used. Put("This is Ada"); would have to be changed to read Text_IO.Put("This is Ada");, which clutters up the listing a bit but removes all ambiguity. Because it is possible to get a different procedure than the one you are expecting under very unusual conditions of overloaded procedure names, the use of the use clause is falling into some disrepute in the software engineering literature. Without the use clause, you are forced to type in additional information for each procedure call, but it leads to no ambiguity and therefore follows the basic premise of Ada that a program is written once but read many times. The extra keystrokes are worth the trouble to include them. Use of the use clause is a matter of personal taste or possibly a style dictated by a project style guide. RENAMING A PROCEDURE _________________________________________________________________ A procedure can be renamed in order to reduce the length of the identifier, especially if a rather long extended name is required. It may be better to use a more descriptive name for the actual use of a generic or general purpose procedure. The method of renaming is illustrated in lines 19 and 20 of this program. Of most importance is the fact that the entire list of formal parameters must be repeated. This is done so that the definition of the new procedure name is complete and should be of help during program debugging. Use of the new name, which is of course only a synonym and not a new procedure, is illustrated in line 35 of this program. If you compiled the previous file, you can compile, link, and execute this one to see that the system knows how to link the two together. Page 15-5 Chapter 15 - Packages COMBINING FILES _________________________________________________________________ If you wish, you could combine the two files, provided you appended the second file to the end of the first. The compiler would then compile all three in succession, after which you could link the results, and execute the result. The library files must be compiled first so that the compiler can check the types in the procedure call to see that they agree, so putting them before the calling program conforms to this rule. If you did put them in a single file, you would still need the statements in lines 5 and 6 to tell the system to with and use the library file defined earlier in the file, because the compiler would consider them to be three separate compilations. ANOTHER METHOD OF COMBINING FILES _________________________________________________________________ The example program named ADDER2.ADA illustrates ================ another way to combine the last two files, in ADDER2.ADA this case including the package in the ================ declaration part of the program. The specification part of the package is in lines 15 through 19, and the body is in lines 27 through 38. In this case, a new type is defined between the two parts to illustrate that it can be done. Since the package is compiled as a part of the main program, it does not have to be mentioned in a with statement. The compiler knows that it is a part of the program, but the use must be mentioned to tell the system where to get the procedure name and the type. Of course the use can be omitted and the extended naming convention used for all references to the package. Even though the body is defined after the variable New_Array is declared in line 23, this variable is not directly available for use in the body, because the package structure effectively builds a strong wall around the enclosed statements, and nothing can get in or out. The only inputs to and outputs from the body are those defined in the specification part of the package. Of course the variable New_Array is available to the procedure because it is passed in as a parameter, but is not directly visible. One other difference occurs here that is different from the last two files. This embedded package is not available for use by any other program, because it is enclosed within this program, and is not therefore a library package. The entire file contains only one compilation unit. Page 15-6 Chapter 15 - Packages ORDER OF DECLARATIONS _________________________________________________________________ This is a fine point, but a very important point if you get a compile error that you do not understand. If you were allowed to include a variable declaration after you declare a procedure, it is possible for the tiny variable declaration to get lost, or at least hard to find in the listing. The designers of Ada decided to force you to put the little things first and the bigger things last by making a rule that says, "No variable, constant, type, or subtype can be declared in any declaration block following the declaration of a procedure, function or package body." It may be an unnecessary rule, but it does exist and is checked by all validated Ada compilers. You should compile and execute this program and you will see that it truly is identical to the previous two files combined. ANOTHER KIND OF SEPARATE COMPILATION _________________________________________________________________ The example file named ADDER3.ADA illustrates ================ still another method of separate compilation. ADDER3.ADA This program is identical to the last except ================ that the package body is removed to a separate file for separate compilation, and is called a stub. The statement in line 27 indicates to the compiler that the body will be found elsewhere. Although this illustrates separate compilation of a package body, the same method can be used for separate compilation of a procedure. This could be used if you wished to remove a large procedure from the logic defined here to make it more manageable. The separately compiled body is found in the ================ file named ADDERSTB.ADA, which begins with the ADDERSTB.ADA reserved word separate which indicates that this ================ is a stub. The main program, or whatever other package, procedure, or function, that uses this stub is defined in parentheses following the reserved word separate to tell the compiler where this is used. This stub cannot be called or used by any other program, because it is in truth part of the program Adder3, not a general purpose program. Any variables, types, procedures, etc, that are available for use at the point where the stub is used, are available at the point where the stub is defined. The stub therefore has the same environment as the environment existing at the point of use, in this case line 27 of ADDER3.ADA. ORDER OF COMPILATION FOR THE STUB _________________________________________________________________ Since all of the variables, types, etc must be made available to the stub, the using program must be compiled before the stub itself Page 15-7 Chapter 15 - Packages is compiled. After both are compiled, the program can be linked and executed. You should compile ADDER3.ADA first, then ADDERSTB.ADA should be compiled, and finally ADDER3 should be linked. You will then have an executable ADDER3 program. ORDER OF COMPILATION IS NOT MYSTERIOUS _________________________________________________________________ The example files included with this chapter are intended to illustrate to you the required order of compilation in a meaningful way, not as a group of rules to be memorized. If you understand the dependencies of files on one another, the order of compilation will make sense, and you will be able to intelligently arrange your programs to use the Ada type checking between the various separately compiled files. Remember that Ada, unlike Pascal, was designed to allow the development of huge programs that require separate compilation facilities, and yet retain the strong type checking between modules. A PACKAGE WITH AN INITIALIZATION PART _________________________________________________________________ The example file named ADDER4.ADA illustrates ================ one more feature of a package. This program is ADDER4.ADA nearly identical to the first program in this ================ chapter. Both files which comprise the first example program have been incorporated into a single file for ease of compilation, and the variable named Total has been made global within the package. Since it is global within the package, it can be referred to by any subprogram in the package or in the initialization section as illustrated in lines 23 and 24. This section is optional but can be included in any package. Note that the initialization section is only executed once, at load time. For that reason, this program will not operate exactly as the first one did. The result that is output is identical in both cases, but the additional calls will not produce the same result because the variable Total is not cleared to zero during each call in this example program as it is in the first example. The initialization section only initializes the variable once, and it is impossible to call this section of code from within the executable part of the package. Page 15-8 Chapter 15 - Packages PROGRAMMING EXERCISES _________________________________________________________________ 1. Remove the package body from ADDER2.ADA and make it a stub. Compile the two resulting files in the correct order and link and execute the resulting program. 2. Remove all use clauses from the program named FORMATS.ADA in the last chapter, and prefix the I/O procedure calls with the proper package names. It should be clear to you that there can be no naming conflicts after this is done. 3. Change the name of the two compilation units in ADDERPKG.ADA without modifying the filename, and modify ADDER1.ADA to correspond to the new package name. Use a name that contains more characters than your operating system permits for a filename. Compile each file and link them together to result in an executable program. Page 15-9